﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Xml;
using System.Security.Cryptography;

namespace UDS
{
    /// <summary>
    /// Defines the callback method for serializing an element.
    /// </summary>
    public delegate void WriteElement(XmlWriter writer, object obj);

    public delegate void ReadElement(XmlReader reader);

    /// <summary>
    /// Some of utility methods.
    /// </summary>
    public static class Utility
    {
        private const long TicksPerMS = (long)1e4;
        private static readonly long JSDateMinTicks = new DateTime(1970, 1, 1, 0, 0, 0).Ticks;
        private static char[] s_hexChars = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
        private static Random _rnd;

        public static int[] SplitToIntegerArray(string value)
        {
            string[] array = Utility.SplitArray(value);
            if (array != null && array.Length > 0)
            {
                List<int> list = new List<int>(array.Length);
                foreach (string str in array)
                {
                    try
                    {
                        list.Add(int.Parse(str));
                    }
                    catch (ArgumentNullException ex1)
                    {
                        throw new InvalidOperationException(string.Format("Invalid comma-seperated list: {0}", value), ex1);
                    }
                    catch (FormatException ex2)
                    {
                        throw new InvalidOperationException(string.Format("Invalid comma-seperated list: {0}", value), ex2);
                    }
                    catch (OverflowException ex3)
                    {
                        throw new InvalidOperationException(string.Format("Invalid comma-seperated list: {0}", value), ex3);
                    }
                }

                return list.ToArray();
            }

            return new int[0];
        }

        public static string[] SplitArray(string value)
        {
            if (!string.IsNullOrEmpty(value))
            {
                string[] array = value.Split(new char[] { ',', '\uFF0C', ';', '\uFF1B' }, StringSplitOptions.RemoveEmptyEntries);
                if (array != null && array.Length > 0)
                {
                    List<string> list = new List<string>(array.Length);
                    for (int i = 0; i < array.Length; i++)
                    {
                        string name = array[i];
                        if (name != null)
                        {
                            name = name.Trim();
                            if (name.Length > 0)
                            {
                                list.Add(name);
                            }
                        }
                    }

                    return list.ToArray();
                }
            }

            return new string[0];
        }

        public static string JoinNames(string[] names)
        {
            return (names == null) ? string.Empty : string.Join("; ", names);
        }

        /// <summary>
        /// Merge two or more arrays into one and remove duplicate items using the default comparer.
        /// </summary>
        public static T[] MergeArray<T>(T[] array1, T[] array2, params T[][] additionalArrays)
        {
            return MergeArray(Comparer<T>.Default, array1, array2, additionalArrays);
        }

        /// <summary>
        /// Merge two or more arrays into one and remove duplicate items.
        /// </summary>
        public static T[] MergeArray<T>(IComparer<T> comparer, T[] array1, T[] array2, params T[][] additionalArrays)
        {
            if (comparer == null)
            {
                throw new ArgumentNullException("comparer");
            }

            List<T> list = new List<T>(256);

            // compute the initial array size\
            if (array1 != null)
            {
                list.AddRange(array1);
            }
            if (array2 != null)
            {
                list.AddRange(array2);
            }
            if (additionalArrays != null)
            {
                foreach (T[] array in additionalArrays)
                {
                    if (array != null)
                    {
                        list.AddRange(array);
                    }
                }
            }

            if (list.Count > 1)
            {
                // Sort the list, and remove duplicate items
                list.Sort(comparer);
                for (int i = list.Count - 1; i > 0; i--)
                {
                    if (0 == comparer.Compare(list[i], list[i - 1]))
                    {
                        list.RemoveAt(i);
                    }
                }
            }

            return list.ToArray();
        }

        #region Date & Time

        public const string DateFormat = "yyyy/MM/dd";
        public const string TimeFormat = "HH:mm";
        public const string DateTimeFormat = DateFormat + " " + TimeFormat;

        public static string FormatDate(DateTime d)
        {
            return d == DateTime.MinValue ? "-" : d.ToString(DateFormat);
        }

        public static string FormatTime(DateTime d)
        {
            return d == DateTime.MinValue ? "-" : d.ToString(TimeFormat);
        }

        public static string FormatDateTime(DateTime d)
        {
            return d == DateTime.MinValue ? "-" : d.ToString(DateTimeFormat);
        }

        #endregion Date & Time

        public static int GetRandomNumber(int max)
        {
            if (_rnd == null)
            {
                _rnd = new Random();
            }
            return _rnd.Next(max);
        }

        public static string JoinArray(IEnumerable<int> array, char sep)
        {
            if (array == null)
            {
                return string.Empty;
            }

            var sb = new StringBuilder(1024);
            bool first = true;
            foreach (var value in array)
            {
                if (!first)
                {
                    sb.Append(sep);
                }
                sb.Append(value);
                first = false;
            }

            return sb.ToString();
        }

        #region Virtual Path

        public static string ResolvePath(string path)
        {
            if (!string.IsNullOrEmpty(path) && path.Length >= 2)
            {
                if (path[0] == '~' && (path[1] == '/' || path[1] == '\\'))
                {
                    var root = HttpRuntime.AppDomainAppVirtualPath;
                    if (!string.IsNullOrEmpty(root))
                    {
                        var lastChar = root[root.Length - 1];
                        if (lastChar == '/' || lastChar == '\\')
                        {
                            path = root + path.Substring(2);
                        }
                        else
                        {
                            path = root + '/' + path.Substring(2);
                        }
                    }
                }
            }
            return path;
        }

        /// <summary>
        /// Replaces '\' as '/', and removes all the extra '/' characters.
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// <i>virtualPath</i> is null.
        /// </exception>
        public static string FixVirtualPathSlashes(string virtualPath)
        {
            if (virtualPath == null)
            {
                throw new ArgumentNullException("virtualPath");
            }

            virtualPath = virtualPath.Replace('\\', '/');
            while (true)
            {
                // Removes all the extra '/' characters
                //
                string replacedPath = virtualPath.Replace("//", "/");
                if (replacedPath == virtualPath)
                {
                    break;
                }

                virtualPath = replacedPath;
            }

            return virtualPath;
        }

        /// <summary>
        /// Combines 2 virtual directory pathes.
        /// </summary>
        public static string CombineVirtualDirs(string vDir1, string vDir2)
        {
            if (string.IsNullOrEmpty(vDir1))
            {
                return vDir2;
            }
            if (string.IsNullOrEmpty(vDir2))
            {
                return vDir1;
            }

            if (IsAppRelativePath(vDir2) || IsRooted(vDir2))
            {
                return vDir2;
            }

            return FixVirtualPathSlashes(vDir1 + '/' + vDir2);
        }

        /// <summary>
        /// Determines whether the specified virtualFilePath is application relative.
        /// The first character of a app-relative virtualFilePath must be '~'.
        /// </summary>
        private static bool IsAppRelativePath(string path)
        {
            if (path == null)
            {
                return false;
            }

            int pathLength = path.Length;
            if (pathLength == 0)
            {
                return false;
            }

            if (path[0] != '~')
            {
                return false;
            }

            if (pathLength > 1 && path[1] != '\\')
            {
                return path[1] == '/';
            }

            return true;
        }

        /// <summary>
        /// Determines whether the specified virtualFilePath is rooted.
        /// </summary>
        private static bool IsRooted(string path)
        {
            if (!string.IsNullOrEmpty(path) && path[0] != '/')
            {
                return path[0] == '\\';
            }

            return true;
        }

        #endregion

        #region SQL

        public static bool ReadBoolean(object val, bool defaultValue)
        {
            return (val == null || val == DBNull.Value) ? defaultValue : Convert.ToBoolean(val);
        }

        public static int ReadInt32(object val, int defaultValue)
        {
            return (val == null || val == DBNull.Value) ? defaultValue : Convert.ToInt32(val);
        }

        public static string ReadString(object val)
        {
            return (val == null || val == DBNull.Value) ? null : Convert.ToString(val);
        }

        /// <summary>
        /// Returns a valid SQL date value.
        /// </summary>
        public static DateTime GetValidSQLDate(DateTime d)
        {
            if (d < SqlDateTime.MinValue.Value || d > SqlDateTime.MaxValue.Value)
            {
                return new DateTime(1900, 1, 1);
            }
            return d.Date;
        }

        public static DateTime ReadSQLDate(object obj)
        {
            if (obj == null || obj == DBNull.Value)
            {
                return DateTime.MinValue;
            }
            return (DateTime)obj;
        }

        public static string ReadSQLDateAsString(object obj)
        {
            if (obj == null || obj == DBNull.Value)
            {
                return "-";
            }
            var date = (DateTime)obj;
            var minDate = new DateTime(1900, 1, 1);
            if (date.Date <= minDate)
            {
                return "-";
            }
            return date.ToString("yyyy-MM-dd");
        }

        #endregion

        public static string ComputeHash(string s)
        {
            if (string.IsNullOrEmpty(s))
            {
                throw new ArgumentNullException("s");
            }

            var buffer = Encoding.GetEncoding(1252).GetBytes(s);
            var hashArray = new MD5CryptoServiceProvider().ComputeHash(buffer);

            var sb = new StringBuilder(hashArray.Length * 2);
            foreach (byte b in hashArray)
            {
                sb.Append(s_hexChars[(b >> 4) & 0x0F]);
                sb.Append(s_hexChars[b & 0x0F]);
            }
            return sb.ToString();
        }

        /// <summary>
        /// Get the value for a Javascript Date object.
        /// </summary>
        public static long GetJSDateValue(DateTime dt)
        {
            var ticks = dt.Ticks - JSDateMinTicks;
            return ticks / TicksPerMS;
        }

        /// <summary>
        /// Gets the display name for the specified enum member.
        /// </summary>
        public static string GetEnumDisplayName(object obj)
        {
            var att = GetEnumDisplayNameAttribute(obj);
            if (att != null)
            {
                return att.Name;
            }

            // The enum member is either not defined, or no DisplayNameAttribute is declared.
            // We simply call ToString on it.
            return obj.ToString();
        }

        /// <summary>
        /// Get the declared description for the given enum member.
        /// </summary>
        public static string GetEnumDescription(object obj)
        {
            var att = GetEnumDisplayNameAttribute(obj);
            if (att != null)
            {
                return string.IsNullOrEmpty(att.Description) ? att.Name : att.Description;
            }

            // The enum member is either not defined, or no DisplayNameAttribute is declared.
            // We simply call ToString on it.
            return obj.ToString();
        }

        private static DisplayNameAttribute GetEnumDisplayNameAttribute(object obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }
            var type = obj.GetType();
            if (!type.IsEnum)
            {
                throw new ArgumentException(string.Format("The specified type '{0}' is not an Enum one.", type.FullName));
            }
            if (Enum.IsDefined(type, obj))
            {
                var field = type.GetField(obj.ToString(), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public);
                if (field == null)
                {
                    throw new InvalidOperationException(string.Format("The field '{0}' cannot be found in type '{1}'.", obj.ToString(), type.FullName));
                }

                // Gets all the DisplayNameAttribute
                object[] attributes = field.GetCustomAttributes(typeof(DisplayNameAttribute), false);
                if (attributes != null && attributes.Length > 0)
                {
                    var displayNameAttr = (DisplayNameAttribute)attributes[0];
                    if (displayNameAttr.Visible)
                    {
                        return displayNameAttr;
                    }
                }
            }

            // Not found!
            return null;
        }

        /// <summary>
        /// Get the custom display names of the specified enum type.
        /// </summary>
        public static IDictionary<int, DisplayNameAttribute> GetEnumDisplayNames(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            if (!type.IsEnum)
            {
                throw new ArgumentException(string.Format("The type '{0}' must be enum type.", type.FullName));
            }

            var dict = new SortedDictionary<int, DisplayNameAttribute>();
            var fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public);
            foreach (FieldInfo f in fields)
            {
                int value = Convert.ToInt32(f.GetValue(null));
                object[] attributes = f.GetCustomAttributes(typeof(DisplayNameAttribute), false);
                if (attributes != null && attributes.Length > 0)
                {
                    var displayNameAttr = (DisplayNameAttribute)attributes[0];
                    Debug.Assert(displayNameAttr != null);

                    if (displayNameAttr.Visible)
                    {
                        dict[value] = displayNameAttr;
                    }
                }
            }

            return dict;
        }

        /// <summary>
        /// Validates the specified parameter.
        /// </summary>
        public static bool ValidateParameter(ref string param, bool checkForNull, bool checkIfEmpty, bool checkForCommas, int maxSize)
        {
            if (param == null)
            {
                return !checkForNull;
            }
            param = param.Trim();
            if ((!checkIfEmpty || param.Length > 0) &&
                (maxSize <= 0 || param.Length <= maxSize) &&
                (!checkForCommas || param.IndexOf(',') < 0))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Tries to get a boolean value.
        /// </summary>
        public static bool GetBooleanValue(NameValueCollection nameValues, string valueName, bool defaultValue)
        {
            if (string.IsNullOrEmpty(valueName))
            {
                throw new ArgumentNullException("valueName");
            }

            string txt = nameValues[valueName];
            if (txt == null)
            {
                return defaultValue;
            }

            bool booleanValue;
            if (!bool.TryParse(txt, out booleanValue))
            {
                throw new ArgumentException(string.Format("The value of '{0}' must be a boolean value.", valueName));
            }

            return booleanValue;
        }

        /// <summary>
        /// Tries to get a non-negative integer, or a positive integer.
        /// </summary>
        public static int GetInt32Value(NameValueCollection nameValues, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed)
        {
            if (string.IsNullOrEmpty(valueName))
            {
                throw new ArgumentNullException("valueName");
            }

            string txt = nameValues[valueName];
            if (txt == null)
            {
                return defaultValue;
            }

            int integer;
            if (!int.TryParse(txt, out integer))
            {
                // Invalid settings for the specified key.
                if (zeroAllowed)
                {
                    throw new ArgumentException(string.Format("The value of '{0}' must be a non-negative integer.", valueName));
                }
                throw new ArgumentException(string.Format("The value of '{0}' must be a positive integer.", valueName));
            }

            if (zeroAllowed && (integer < 0))
            {
                throw new ArgumentException(string.Format("The value of '{0}' must be a non-negative integer.", valueName));
            }
            if (!zeroAllowed && (integer <= 0))
            {
                throw new ArgumentException(string.Format("The value of '{0}' must be a positive integer.", valueName));
            }
            if ((maxValueAllowed > 0) && (integer > maxValueAllowed))
            {
                throw new ArgumentException(string.Format("The value of '{0}' should be less that {1}.", valueName, maxValueAllowed));
            }
            return integer;
        }

        /// <summary>
        /// The generated xml looks like:
        /// [root]
        ///     [elm id="" /]
        ///     [elm id="" /]
        ///     ....
        /// [/root]
        /// </summary>
        public static string GenerateXML(IEnumerable e, string root, WriteElement w)
        {
            if (string.IsNullOrEmpty(root))
            {
                throw new ArgumentNullException("root");
            }
            if (w == null)
            {
                throw new ArgumentNullException("w");
            }

            using (var sw = new StringWriter())
            {
                var settings = new XmlWriterSettings();
                settings.ConformanceLevel = ConformanceLevel.Document;
                settings.OmitXmlDeclaration = true;
                settings.Indent = false;

                using (XmlWriter writer = XmlWriter.Create(sw, settings))
                {
                    writer.WriteStartElement(root);
                    if (e != null)
                    {
                        foreach (object obj in e)
                        {
                            if (obj != null)
                            {
                                w(writer, obj);
                            }
                        }
                    }
                    writer.WriteEndElement();
                }

                return sw.GetStringBuilder().ToString();
            }
        }

        public static string GenerateXML<T>(IEnumerable<T> array, string root, string elementName, string attributeName)
        {
            using (var sw = new StringWriter())
            {
                var settings = new XmlWriterSettings();
                settings.ConformanceLevel = ConformanceLevel.Document;
                settings.OmitXmlDeclaration = true;
                settings.Indent = false;

                using (XmlWriter writer = XmlWriter.Create(sw, settings))
                {
                    writer.WriteStartElement(root);
                    if (array != null)
                    {
                        foreach (T value in array)
                        {
                            writer.WriteStartElement(elementName);
                            writer.WriteAttributeString(attributeName, value.ToString());
                            writer.WriteEndElement();
                        }
                    }
                    writer.WriteEndElement();
                }

                return sw.GetStringBuilder().ToString();
            }
        }

        public static void ReadXML(string xml, string root, string elementName, ReadElement r)
        {
            if (!string.IsNullOrEmpty(xml))
            {
                using (var sr = new StringReader(xml))
                {
                    using (XmlReader reader = XmlReader.Create(sr))
                    {
                        ReadXML(reader, root, elementName, r);
                    }
                }
            }
        }

        public static void ReadXML(IDataReader dataReader, string columnName, string root, string elementName, ReadElement r)
        {
            ReadXML(Convert.ToString(dataReader[columnName]), root, elementName, r);
        }

        public static void ReadXML(XmlReader reader, string root, string elementName, ReadElement r)
        {
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }

            reader.MoveToContent();
            if (reader.NodeType != XmlNodeType.Element)
            {
                // Empty document?
                return;
            }

            if (reader.LocalName != root)
            {
                throw new InvalidOperationException("Unknown root node: " + reader.LocalName);
            }

            // Move to first element
            reader.ReadStartElement();
            reader.MoveToContent();
            while (reader.NodeType != XmlNodeType.EndElement && reader.NodeType != XmlNodeType.None)
            {
                if (reader.NodeType == XmlNodeType.Element
                    && reader.LocalName == elementName
                    && reader.HasAttributes)
                {
                    if (r != null)
                    {
                        r(reader);
                    }
                }

                reader.Skip();
            }
        }

        internal static string EnsureEndWithSemiColon(string value)
        {
            if (value != null)
            {
                int length = value.Length;
                if (length > 0 && value[length - 1] != ';')
                {
                    return value + ";";
                }
            }
            return value;
        }

        internal static string MergeScript(string first, string second)
        {
            if (!string.IsNullOrEmpty(first))
            {
                return first + second;
            }

            if (string.IsNullOrEmpty(second))
            {
                return first;
            }

            second = second.TrimStart(null);
            if (second.StartsWith("javascript:", StringComparison.Ordinal))
            {
                return second;
            }

            return "javascript:" + second;
        }
    }
}